#include <QApplication>
#include <QScreen>
#include <QDir>
#include <QColorDialog>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QUiLoader>
#include <QBuffer>
#include <QByteArray>
#include <QString>
#include <QVector>
#include <QVariant>
#include <QResizeEvent>
#include <QAction>
#include <QIcon>
#include <QPixmap>
#include <QListWidget>
#include <QListWidgetItem>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVariantMap>
#include <QVariantList>
#include <QUuid>
#include <QWebEngineView>
#include <QWebEngineUrlScheme>
#include <QSqlDatabase>
#include <QSqlQuery>
#include "util.hpp"
#include "web.hpp"
#include "qtbinding.h"


class UiLoader: public QUiLoader {
public:
    virtual QWidget* createWidget(const QString &className, QWidget *parent = nullptr, const QString &name = QString()) override {
        if (className == "QWebEngineView") {
            QWidget* w = new QWebEngineView(parent);
            w->setObjectName(name);
            return w;
        } else if (className == "WebView") {
            QWidget* w = new WebView(parent, false);
            w->setObjectName(name);
            return w;
        } else {
            return QUiLoader::createWidget(className, parent, name);
        }
    }
};

const size_t QtEventMove = QEvent::Move;
const size_t QtEventResize = QEvent::Resize;
const size_t QtEventClose = QEvent::Close;
const size_t QtEventFocusIn = QEvent::FocusIn;
const size_t QtEventFocusOut = QEvent::FocusOut;
const size_t QtEventDynamicPropertyChange = QEvent::DynamicPropertyChange;

const size_t QtWindowKindOrdinary = 0;
const size_t QtWindowKindDialog = 1;
const size_t QtWindowKindPopup = 2;

static QApplication*
    app = nullptr;
static CallbackExecutor*
    executor = nullptr;
static UiLoader*
    loader = nullptr;
static bool
    initialized = false;

void QtInit() {
    static int fake_argc = 1;
    static char fake_arg[] = {'Q','t','A','p','p','\0'};
    static char* fake_argv[] = { fake_arg };
    if (!(initialized)) {
        // schemes need to be configured before a QGuiApplication or QApplication instance is created
        QWebEngineUrlScheme scheme(WebAssetScheme);
        scheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
        scheme.setDefaultPort(QWebEngineUrlScheme::SpecialPort::PortUnspecified);
        scheme.setFlags(
            QWebEngineUrlScheme::SecureScheme |
            QWebEngineUrlScheme::CorsEnabled |
            QWebEngineUrlScheme::LocalScheme |
            QWebEngineUrlScheme::LocalAccessAllowed
        );
        QWebEngineUrlScheme::registerScheme(scheme);
        // ------------------------
        app = new QApplication(fake_argc, fake_argv);
        app->setQuitOnLastWindowClosed(false);
        qRegisterMetaType<callback_t>();
        executor = new CallbackExecutor();
        loader = new UiLoader();
        initialized = true;
    }
}

int QtMain() {
    return app->exec();
}

void QtCommitTask(callback_t cb, size_t payload) {
    emit executor->QueueCallback(cb, payload);
}

void QtExit(int code) {
    app->exit(code);
}

void QtQuit() {
    app->quit();
}

QtString QtNewUUID() {
    return WrapString(QUuid::createUuid().toString());
}

void* QtLoadWidget(const char* definition, const char* directory) {
    QByteArray bytes(definition);
    QBuffer buf(&bytes);
    QDir dir(directory);
    loader->setWorkingDirectory(dir);
    QWidget* widget = loader->load(&buf, nullptr);
    return (void*) widget;
}

void* QtObjectFindChild(void* object_ptr, const char* name) {
    QObject* obj = (QObject*) object_ptr;
    QObject* child = obj->findChild<QObject*>(QString(name));
    return (void*) child;
}

void* QtWidgetFindChildWidget(void* widget_ptr, const char* name) {
    QWidget* widget = (QWidget*) widget_ptr;
    QWidget* child = widget->findChild<QWidget*>(QString(name));
    return (void*) child;
}

void* QtWidgetFindChildAction(void* widget_ptr, const char* name) {
    QWidget* widget = (QWidget*) widget_ptr;
    QAction* child = widget->findChild<QAction*>(QString(name));
    return (void*) child;
}

void QtWidgetShow(void* widget_ptr) {
    QWidget* widget = (QWidget*) widget_ptr;
    widget->show();
}

void QtWidgetHide(void* widget_ptr) {
    QWidget* widget = (QWidget*) widget_ptr;
    widget->hide();
}

void QtWidgetMoveToScreenCenter(void* widget_ptr) {
    QWidget* widget = (QWidget*) widget_ptr;
    MoveToScreenCenter(widget);
}

QtString QtObjectGetClassName(void* obj_ptr) {
    QObject* obj = (QObject*) obj_ptr;
    return WrapString(obj->metaObject()->className());
}

QtBool QtObjectSetPropBool(void* obj_ptr, const char* prop, QtBool val) {
    QObject* obj = (QObject*) obj_ptr;
    return obj->setProperty(prop, (val != 0));
}

QtBool QtObjectGetPropBool(void* obj_ptr, const char* prop) {
    QObject* obj = (QObject*) obj_ptr;
    QVariant val = obj->property(prop);
    return val.toBool();
}

QtBool QtObjectSetPropString(void* obj_ptr, const char* prop, QtString val) {
    QObject* obj = (QObject*) obj_ptr;
    return obj->setProperty(prop, UnwrapString(val));
}

QtString QtObjectGetPropString(void* obj_ptr, const char* prop) {
    QObject* obj = (QObject*) obj_ptr;
    QVariant val = obj->property(prop);
    return WrapString(val.toString());
}

QtBool QtObjectSetPropInt(void* obj_ptr, const char* prop, int val) {
    QObject* obj = (QObject*) obj_ptr;
    return obj->setProperty(prop, val);
}

int QtObjectGetPropInt(void* obj_ptr, const char* prop) {
    QObject* obj = (QObject*) obj_ptr;
    QVariant val = obj->property(prop);
    return val.toInt();
}

QPoint UnwrapPoint(QtPoint p) {
    return QPoint(p.x, p.y);
}

QtPoint WrapPoint(QPoint p) {
    return { p.x(), p.y() };
}

QtConnHandle QtConnect (
        void* obj_ptr,
        const char* signal,
        callback_t cb,
        size_t payload
) {
    QObject* target_obj = (QObject*) obj_ptr;
    CallbackObject* cb_obj = new CallbackObject(target_obj, cb, payload);
    ConnectionHandle* handle = new ConnectionHandle;
    handle->conn = QtDynamicConnect(target_obj, signal, cb_obj, "slot()");
    handle->cb_obj = cb_obj;
    return { (void*) handle };
}

QtBool QtIsConnectionValid(QtConnHandle handle) {
    ConnectionHandle* h = (ConnectionHandle*) handle.ptr;
    return bool(h->conn);
};

void QtDisconnect(QtConnHandle handle) {
    ConnectionHandle* h = (ConnectionHandle*) handle.ptr;
    if (!(h->conn)) {
        QObject::disconnect(h->conn);
    }
    delete h->cb_obj;
    h->cb_obj = nullptr;
    delete h;
};

void QtBlockSignals(void* obj_ptr, QtBool block) {
    QObject* obj = (QObject*) obj_ptr;
    obj->blockSignals(bool(block));
}

void QtBlockCallbacks(void* obj_ptr, QtBool block) {
    QObject* obj = (QObject*) obj_ptr;
    obj->setProperty(CALLBACK_BLOCKED, bool(block));
}

QtEventListener QtAddEventListener (
        void*       obj_ptr,
        size_t      kind,
        QtBool      prevent,
        callback_t  cb,
        size_t      payload
) {
    QObject* obj = (QObject*) obj_ptr;
    QEvent::Type q_kind = (QEvent::Type) kind;
    EventListener* l = new EventListener(q_kind, prevent, cb, payload);
    obj->installEventFilter(l);
    QtEventListener wrapped;
    wrapped.ptr = l;
    return wrapped;
}

QtEvent QtGetCurrentEvent(QtEventListener listener) {
    EventListener* l = (EventListener*) listener.ptr;
    QtEvent wrapped;
    wrapped.ptr = l->current_event;
    return wrapped;
}

void QtRemoveEventListener(void* obj_ptr, QtEventListener listener) {
    QObject* obj = (QObject*) obj_ptr;
    EventListener* l = (EventListener*) listener.ptr;
    obj->removeEventFilter(l);
    delete l;
}

size_t QtResizeEventGetWidth(QtEvent ev) {
    QResizeEvent* resize = (QResizeEvent*) ev.ptr;
    return resize->size().width();
}

size_t QtResizeEventGetHeight(QtEvent ev) {
    QResizeEvent* resize = (QResizeEvent*) ev.ptr;
    return resize->size().height();
}

QtString QtDynamicPropertyChangeEventGetPropertyName(QtEvent ev) {
    QDynamicPropertyChangeEvent* change = (QDynamicPropertyChangeEvent*) ev.ptr;
    return WrapString(QString::fromUtf8(change->propertyName()));
}

QtPoint QtMakePoint(int x, int y) {
    return { x, y };
}

int QtPointGetX(QtPoint p) {
    return p.x;
}

int QtPointGetY(QtPoint p) {
    return p.y;
}

QtString QtNewStringUTF8(const uint8_t* buf, size_t len) {
    QString* ptr = new QString;
    *ptr = QString::fromUtf8((const char*)(buf), len);
    return { (void*) ptr };
}

QtString QtNewStringUTF32(const uint32_t* buf, size_t len) {
    QString* ptr = new QString;
    *ptr = QString::fromUcs4(buf, len);
    return { (void*) ptr };
}

void QtDeleteString(QtString str) {
    delete (QString*)(str.ptr);
}

size_t QtStringUTF16Length(QtString str) {
    return UnwrapString(str).length();
}

size_t QtStringWriteToUTF32Buffer(QtString str, uint32_t *buf) {
    const QVector<uint> vec = UnwrapString(str).toUcs4();
    size_t len = 0;
    for(auto rune: vec) {
        *buf = rune;
        buf += 1;
        len += 1;
    }
    return len;
}

size_t QtStringListGetSize(QtStringList list) {
    QStringList* ptr = (QStringList*) (list.ptr);
    return ptr->size();
}

QtString QtStringListGetItem(QtStringList list, size_t index) {
    QStringList* ptr = (QStringList*) (list.ptr);
    return WrapString(ptr->at(index));
}

void QtDeleteStringList(QtStringList list) {
    delete (QStringList*)(list.ptr);
}

uint8_t* QtByteArrayGetBuffer(QtByteArray data) {
    QByteArray* ptr = (QByteArray*) (data.ptr);
    return (uint8_t*) ptr->data();
}

size_t QtByteArrayGetSize(QtByteArray data) {
    QByteArray* ptr = (QByteArray*) (data.ptr);
    return ptr->size();
}

void QtDeleteByteArray(QtByteArray data) {
    delete (QByteArray*)(data.ptr);
}

QtVariantList QtNewVariantList() {
    return { (void*) new QVariantList() };
}

void QtVariantListAppendNumber(QtVariantList l, double n) {
    QVariantList* ptr = (QVariantList*) l.ptr;
    ptr->append(n);
}

void QtVariantListAppendString(QtVariantList l, QtString str) {
    QVariantList* ptr = (QVariantList*) l.ptr;
    ptr->append(UnwrapString(str));
}

void QtDeleteVariantList(QtVariantList l) {
    delete (QVariantList*)(l.ptr);
}

QtString QtVariantMapGetString(QtVariantMap m, QtString key) {
    QVariantMap* ptr = (QVariantMap*) m.ptr;
    QString key_ = UnwrapString(key);
    QVariant val_ = (*ptr)[key_];
    QtString val = WrapString(val_.toString());
    return val;
}

double QtVariantMapGetFloat(QtVariantMap m, QtString key) {
    QVariantMap* ptr = (QVariantMap*) m.ptr;
    QString key_ = UnwrapString(key);
    QVariant val_ = (*ptr)[key_];
    double val = val_.toDouble();
    return val;
}

QtBool QtVariantMapGetBool(QtVariantMap m, QtString key) {
    QVariantMap* ptr = (QVariantMap*) m.ptr;
    QString key_ = UnwrapString(key);
    QVariant val_ = (*ptr)[key_];
    int val = val_.toBool();
    return val;
}

void QtDeleteVariantMap(QtVariantMap m) {
    delete (QVariantMap*)(m.ptr);
}

QtIcon QtNewIcon(QtPixmap pm) {
    QIcon* ptr = new QIcon(*(QPixmap*)(pm.ptr));
    return { (void*) ptr };
}

QtIcon QtNewIconEmpty() {
    QIcon* ptr = new QIcon();
    return { (void*) ptr };
}

void QtDeleteIcon(QtIcon icon) {
    delete (QIcon*)(icon.ptr);
}

QtPixmap QtNewPixmap(const uint8_t* buf, size_t len, const char* format) {
    QPixmap *ptr = new QPixmap;
    ptr->loadFromData(buf, len, format);
    return { (void*) ptr };
}

QtPixmap QtNewPixmapPNG(const uint8_t* buf, size_t len) {
    return QtNewPixmap(buf, len, "PNG");
}

QtPixmap QtNewPixmapJPEG(const uint8_t* buf, size_t len) {
    return QtNewPixmap(buf, len, "JPG");
}

void QtDeletePixmap(QtPixmap pm) {
    delete (QPixmap*)(pm.ptr);
}

void QtListWidgetClear(void* widget_ptr) {
    QListWidget* widget = (QListWidget*) widget_ptr;
    widget->clear();
}

void QtListWidgetAddItem(void* widget_ptr, QtString key_, QtIcon icon_, QtString label_, QtBool as_current) {
    QListWidget* widget = (QListWidget*) widget_ptr;
    QString key = UnwrapString(key_);
    QString label = UnwrapString(label_);
    QIcon* icon = (QIcon*) icon_.ptr;
    QListWidgetItem* item = new QListWidgetItem(*icon, label, widget);
    item->setData(Qt::UserRole, key);
    widget->addItem(item);
    if (as_current) {
        widget->setCurrentItem(item);
    }
}

QtBool QtListWidgetHasCurrentItem(void* widget_ptr) {
    QListWidget* widget = (QListWidget*) widget_ptr;
    return (widget->currentRow() != -1);
}

QtString QtListWidgetGetCurrentItemKey(void* widget_ptr) {
    QListWidget* widget = (QListWidget*) widget_ptr;
    QVariant key_v = widget->currentItem()->data(Qt::UserRole);
    return WrapString(key_v.toString());
}

void QtTreeWidgetClear(void* widget_ptr) {
    QTreeWidget* widget = (QTreeWidget*) widget_ptr;
    widget->clear();
}

size_t QtTreeWidgetGetColumnCount(void* widget_ptr) {
    QTreeWidget* widget = (QTreeWidget*) widget_ptr;
    return widget->columnCount();
}

void* QtTreeWidgetAddItem(void* widget_ptr, void* parent_item_ptr, QtString key_, QtIcon icon_, QtString* labels_, QtBool as_current) {
    QTreeWidget* widget = (QTreeWidget*) widget_ptr;
    QString key = UnwrapString(key_);
    QStringList labels;
    for (int i = 0; i < widget->columnCount(); i += 1) {
        QString label = UnwrapString(labels_[i]);
        labels.append(label);
    }
    QIcon* icon = (QIcon*) icon_.ptr;
    QTreeWidgetItem* item;
    if (parent_item_ptr != nullptr) {
        QTreeWidgetItem* parent_item = (QTreeWidgetItem*) parent_item_ptr;
        item = new QTreeWidgetItem(parent_item, labels);
    } else {
        item = new QTreeWidgetItem(widget, labels);
    }
    item->setIcon(0, *icon);
    item->setData(0, Qt::UserRole, key);
    if (as_current) {
        widget->setCurrentItem(item);
    }
    return (void*) item;
}

QtBool QtTreeWidgetHasCurrentItem(void* widget_ptr) {
    QTreeWidget* widget = (QTreeWidget*) widget_ptr;
    return (widget->currentItem() != nullptr);
}

QtString QtTreeWidgetGetCurrentItemKey(void* widget_ptr) {
    QTreeWidget* widget = (QTreeWidget*) widget_ptr;
    QVariant key_v = widget->currentItem()->data(0, Qt::UserRole);
    return WrapString(key_v.toString());
}

void QtWebViewDisableContextMenu(void* widget_ptr) {
    QWebEngineView* widget = (QWebEngineView*) widget_ptr;
    widget->setContextMenuPolicy(Qt::NoContextMenu);
}

void QtWebViewEnableLinkDelegation(void* widget_ptr) {
    QWebEngineView* widget = (QWebEngineView*) widget_ptr;
    auto g = widget->geometry();
    widget->setPage(new LinkDelegatedPage(widget));
    widget->setGeometry(g);
}

void QtWebViewSetHTML(void* widget_ptr, QtString html, QtString base_url) {
    QWebEngineView* widget = (QWebEngineView*) widget_ptr;
    widget->setHtml(UnwrapString(html), UnwrapString(base_url));
}

void QtWebViewScrollToAnchor(void* widget_ptr, QtString anchor) {
    QWebEngineView* widget = (QWebEngineView*) widget_ptr;
    QJsonObject args;
    args["anchor"] = UnwrapString(anchor);
    QString args_json = QString::fromUtf8(QJsonDocument(args).toJson());
    QString script = QString(
        "(({anchor})=>{document.getElementById(anchor).scrollIntoView()})(%1)"
    ).arg(args_json);
    widget->page()->runJavaScript(script);
}

QtPoint QtWebViewGetScroll(void* widget_ptr) {
    QWebEngineView* widget = (QWebEngineView*) widget_ptr;
    return WrapPoint((widget->page()->scrollPosition()).toPoint());
}

void QtWebViewSetScroll(void* widget_ptr, QtPoint pos) {
    QWebEngineView* widget = (QWebEngineView*) widget_ptr;
    QJsonObject args;
    args["x"] = qreal(pos.x);
    args["y"] = qreal(pos.y);
    QString args_json = QString::fromUtf8(QJsonDocument(args).toJson());
    QString scrolling = "document.scrollingElement";
    QString script = QString(
        "((scrolling,{x,y})=>{scrolling.scrollLeft=x;scrolling.scrollTop=y;})(%1,%2)"
    ).arg(scrolling, args_json);
    widget->page()->runJavaScript(script);
}

void QtDialogExec(void *dialog_ptr) {
    QDialog* dialog = (QDialog*) dialog_ptr;
    dialog->exec();
}

void QtDialogAccept(void *dialog_ptr) {
    QDialog* dialog = (QDialog*) dialog_ptr;
    dialog->accept();
}

void QtDialogReject(void *dialog_ptr) {
    QDialog* dialog = (QDialog*) dialog_ptr;
    dialog->reject();
}

void QtDialogShowModal(void* dialog_ptr) {
    QDialog* dialog = (QDialog*) dialog_ptr;
    dialog->open();
}

void QtDialogSetParent(void* dialog_ptr, void* parent_ptr) {
    QDialog* dialog = (QDialog*) dialog_ptr;
    QWidget* parent = (QWidget*) parent_ptr;
    dialog->setParent(parent);
}

QtBool QtColorDialog(void* parent_ptr, QtString title, size_t* h, size_t* s, size_t* l, size_t* a) {
    QWidget* parent = (QWidget*) parent_ptr;
    QColor initial = QColor::fromHsl(*h, *s, *l, *a);
    QColor selected = QColorDialog::getColor(
        initial,
        parent,
        UnwrapString(title),
        QColorDialog::ShowAlphaChannel
    );
    if (selected.isValid()) {
        int ih, is, il, ia;
        selected.getHsl(&ih, &is, &il, &ia);
        if (ih < 0) {
            ih = 0;
        }
        *h = size_t(ih); *s = size_t(is); *l = size_t(il); *a = size_t(ia);
        return true;
    } else {
        return false;
    }
}

QtString QtFileDialogOpen(void* parent_ptr, QtString title, QtString cwd, QtString filter) {
   QWidget* parent = (QWidget*) parent_ptr;
   QString path = QFileDialog::getOpenFileName(
        parent,
        UnwrapString(title),
        UnwrapString(cwd),
        UnwrapString(filter)
    );
   return WrapString(path);
}

QtStringList QtFileDialogOpenMultiple(void* parent_ptr,  QtString title, QtString cwd, QtString filter) {
    QWidget* parent = (QWidget*) parent_ptr;
    QStringList* path_list = new QStringList;
    *path_list = QFileDialog::getOpenFileNames(
        parent,
        UnwrapString(title),
        UnwrapString(cwd),
        UnwrapString(filter)
    );
    QtStringList wrapped = { path_list };
    return wrapped;
}

QtString QtFileDialogSelectDirectory(void* parent_ptr, QtString title, QtString cwd) {
    QWidget* parent = (QWidget*) parent_ptr;
    QString path = QFileDialog::getExistingDirectory(
        parent,
        UnwrapString(title),
        UnwrapString(cwd)
    );
    return WrapString(path);
}

QtString QtFileDialogSave(void* parent_ptr, QtString title, QtString cwd, QtString filter) {
    QWidget* parent = (QWidget*) parent_ptr;
    QString path = QFileDialog::getSaveFileName(
        parent,
        UnwrapString(title),
        UnwrapString(cwd),
        UnwrapString(filter)
    );
    return WrapString(path);
}

QtBool QtInputDialog(void* parent_ptr, QtString title, QtString prompt, QtBool password, QtString text) {
    QWidget* parent = (QWidget*) parent_ptr;
    QLineEdit::EchoMode mode = QLineEdit::Normal;
    if (password) {
        mode = QLineEdit::Password;
    }
    bool ok;
    *(QString*)(text.ptr) = QInputDialog::getText(
        parent,
        UnwrapString(title),
        UnwrapString(prompt),
        mode,
        UnwrapString(text),
        &ok
    );
    return ok;
}

QtBool QtInputDialogInt(void* parent_ptr, QtString title, QtString prompt, int min, int max, int step, int* value) {
    QWidget* parent = (QWidget*) parent_ptr;
    bool ok;
    *value = QInputDialog::getInt(
        parent,
        UnwrapString(title),
        UnwrapString(prompt),
        *value,
        min,
        max,
        step,
        &ok
    );
    return ok;
}

QtBool QtInputDialogDouble(void* parent_ptr, QtString title, QtString prompt, double min, double max, double step, int decimals, double* value) {
    QWidget* parent = (QWidget*) parent_ptr;
    bool ok;
    *value = QInputDialog::getDouble(
        parent,
        UnwrapString(title),
        UnwrapString(prompt),
        *value,
        min,
        max,
        decimals,
        &ok,
        Qt::WindowFlags(),
        step
    );
    return ok;
}

QtBool QtMessageBoxConfirm(void* parent_ptr, QtString title, QtString content) {
    QWidget* parent = (QWidget*) parent_ptr;
    auto btn = QMessageBox::question(
        parent,
        UnwrapString(title), UnwrapString(content),
        (QMessageBox::Yes | QMessageBox::No),
        QMessageBox::No
    );
    if (btn == QMessageBox::Yes) {
        return true;
    } else {
        return false;
    }
}

void QtMessageBoxError(void* parent_ptr, QtString title, QtString content) {
    QWidget* parent = (QWidget*) parent_ptr;
    QMessageBox::critical(parent, UnwrapString(title), UnwrapString(content));
}

void WebViewLoadContent(void* view_ptr) {
    WebView* view = (WebView*) view_ptr;
    view->LoadContent();
}
void WebViewRegisterAsset(void* view_ptr, QtString key, QtString mime, const uint8_t* buf, size_t len) {
    WebView* view = (WebView*) view_ptr;
    QByteArray data = QByteArray::fromRawData((const char*)(buf), len);
    view->getStore()->InsertItem(UnwrapString(key), UnwrapString(mime), data);
}
QtString WebGetAssetURL(QtString key) {
    // TODO: move this logic to util
    auto url = QString("asset:///%1").arg(EncodeBase64(UnwrapString(key)));
    return WrapString(url);
}
void WebViewInjectCSS(void* view_ptr, QtString key) {
    WebView* view = (WebView*) view_ptr;
    QString key_base64 = EncodeBase64(UnwrapString(key));
    emit view->getBridge()->InjectCSS(key_base64);
}
void WebViewInjectJS(void* view_ptr, QtString key) {
    WebView* view = (WebView*) view_ptr;
    QString key_base64 = EncodeBase64(UnwrapString(key));
    emit view->getBridge()->InjectJS(key_base64);
}
void WebViewInjectTTF(void* view_ptr, QtString key, QtString family, QtString weight, QtString style) {
    WebView* view = (WebView*) view_ptr;
    QString key_base64 = EncodeBase64(UnwrapString(key));
    emit view->getBridge()->InjectTTF(key_base64, UnwrapString(family), UnwrapString(weight), UnwrapString(style));
}
void WebViewCallMethod(void* view_ptr, QtString id, QtString name, QtVariantList args) {
    WebView* view = (WebView*) view_ptr;
    QVariantList args_copy = *(QVariantList*)(args.ptr);
    emit view->getBridge()->CallMethod(UnwrapString(id), UnwrapString(name), args_copy);
}

QtString WebViewGetCurrentEventHandler(void* view_ptr) {
    WebView* view = (WebView*) view_ptr;
    return WrapString(view->getEmittedEventHandler());
}
QtVariantMap WebViewGetCurrentEventPayload(void* view_ptr) {
    WebView* view = (WebView*) view_ptr;
    QVariantMap* m = new QVariantMap;
    *m = view->getEmittedEventPayload();
    return { m };
}
void WebViewPatchActualDOM(void* view_ptr, QtString operations) {
    WebView* view = (WebView*) view_ptr;
    emit view->getBridge()->PatchActualDOM(UnwrapString(operations), view);
}

void* WebWindowCreate(void* parent_ptr, size_t kind, QtIcon icon, QtString title, int width, int height, QtBool closeBtn, QtBool modal, QtBool debug) {
    WebWindow* w = new WebWindow (
        (QWidget*) parent_ptr,
        kind,
        *(QIcon*) icon.ptr,
        UnwrapString(title),
        QSize(width, height),
        bool(closeBtn),
        bool(modal),
        bool(debug)
    );
    return (void*) w;
}
void* WebWindowGetWebView(void* w_ptr) {
    WebWindow* w = (WebWindow*) w_ptr;
    return (void*) w->getWebView();
}
void WebWindowShow(void* w_ptr, QtBool set_pos, int x, int y) {
    WebWindow* w = (WebWindow*) w_ptr;
    return w->showRelative(set_pos, QPoint(x,y));
}
void WebWindowDispose(void* w_ptr) {
    WebWindow* w = (WebWindow*) w_ptr;
    w->hide();
    w->deleteLater();
}

QtBool KvdbOpen(void** handle_ptr, QtString path, QtBool exclusive) {
    QString conn = QUuid::createUuid().toString();
    QSqlDatabase d = QSqlDatabase::addDatabase("QSQLITE", conn);
    d.setDatabaseName(UnwrapString(path));
    if (!(d.open())) {
        return false;
    }
    QSqlQuery q(d);
    if (exclusive) {
        bool ok;
        ok = q.exec("PRAGMA locking_mode = EXCLUSIVE");
        if (!(ok)) { return false; }
        ok = q.exec("BEGIN EXCLUSIVE");
        if (!(ok)) { return false; }
        ok = q.exec("COMMIT");
        if (!(ok)) { return false; }
    }
    if (!(d.tables().contains("kvdb"))) {
        bool ok;
        ok = q.exec("create table kvdb(id integer primary key autoincrement, uniq_key varchar(64) not null unique, category integer not null, value blob not null)");
        if (!(ok)) { return false; }
        ok = q.exec("create unique index idx_kvdb_uniq_key on kvdb(uniq_key)");
        if (!(ok)) { return false; }
        ok = q.exec("create index idx_kvdb_category on kvdb(category)");
        if (!(ok)) { return false; }
    }
    *(QSqlDatabase**)(handle_ptr) = new QSqlDatabase();
    **(QSqlDatabase**)(handle_ptr) = d;
    return true;
}
void KvdbClose(void* handle) {
    QSqlDatabase* d = (QSqlDatabase*) handle;
    QString conn = d->connectionName();
    d->close();
    delete d;
    QSqlDatabase::removeDatabase(conn);
}
QtBool KvdbInsert(void* handle, QtString key, int category, uint8_t* val_buf, size_t val_len) {
    QSqlDatabase d = *(QSqlDatabase*) handle;
    QSqlQuery q(d);
    bool ok;
    ok = q.prepare("insert into kvdb(uniq_key,category,value) values (?,?,?)");
    if (!(ok)) { return false; } {
        q.bindValue(0, UnwrapString(key));
        q.bindValue(1, category);
        q.bindValue(2, QByteArray::fromRawData((char*)val_buf, val_len));
        ok = q.exec();
        if (!(ok)) { return false; }
    }
    return true;
}
QtBool KvdbRemove(void* handle, QtString key) {
    QSqlDatabase d = *(QSqlDatabase*) handle;
    QSqlQuery q(d);
    bool ok;
    ok = q.prepare("delete from kvdb where uniq_key=?");
    if (!(ok)) { return false; } {
        q.bindValue(0, UnwrapString(key));
        ok = q.exec();
        if (!(ok)) { return false; }
    }
    return true;
}
QtBool KvdbUpdate(void* handle, QtString key, uint8_t* val_buf, size_t val_len) {
    QSqlDatabase d = *(QSqlDatabase*) handle;
    QSqlQuery q(d);
    bool ok;
    ok = q.prepare("update kvdb set value=? where uniq_key=?");
    if (!(ok)) { return false; } {
        q.bindValue(0, QByteArray::fromRawData((char*)val_buf, val_len));
        q.bindValue(1, UnwrapString(key));
        ok = q.exec();
        if (!(ok)) { return false; }
    }
    return true;
}
QtBool KvdbLookup(void* handle, QtString key, QtBool* found, QtByteArray* value) {
    QSqlDatabase d = *(QSqlDatabase*) handle;
    QSqlQuery q(d);
    bool ok;
    ok = q.prepare("select value from kvdb where uniq_key=?");
    if (!(ok)) { return false; } {
        q.bindValue(0, UnwrapString(key));
        ok = q.exec();
        if (!(ok)) { return false; }
    }
    if (q.next()) {
        *found = true;
        value->ptr = new QByteArray();
        *(QByteArray*)(value->ptr) = q.value(0).toByteArray();
    } else {
        *found = false;
    }
    return true;
}
QtBool KvdbReadAll(void* handle, KvdbSelectQuery* q) {
    QSqlDatabase d = *(QSqlDatabase*) handle;
    QSqlQuery* ptr = new QSqlQuery(d);
    bool ok;
    ok = ptr->exec("select uniq_key,value from kvdb");
    if (!(ok)) { return false; }
    q->ptr = ptr;
    return true;
}
QtBool KvdbReadCategory(void* handle, int category, KvdbSelectQuery* q) {
    QSqlDatabase d = *(QSqlDatabase*) handle;
    QSqlQuery* ptr = new QSqlQuery(d);
    bool ok;
    ok = ptr->prepare("select uniq_key,value from kvdb where category=?");
    if (!(ok)) { return false; } {
        ptr->bindValue(0, category);
        ok = ptr->exec();
        if (!(ok)) { return false; }
    }
    q->ptr = ptr;
    return true;
}
QtBool KvdbSelectQueryNext(KvdbSelectQuery q, QtString* key, QtByteArray* value) {
    QSqlQuery* ptr = (QSqlQuery*) (q.ptr);
    if (ptr->next()) {
        key->ptr = new QString();
        value->ptr = new QByteArray();
        *(QString*) key->ptr = ptr->value(0).toString();
        *(QByteArray*) value->ptr = ptr->value(1).toByteArray();
        return true;
    } else {
        return false;
    }
}
void KvdbDeleteSelectQuery(KvdbSelectQuery q) {
    delete (QSqlQuery*)(q.ptr);
}


